// 
// task.c - Implements the functionality needed to multitask.
//          Written for JamesM's kernel development tutorials.
//

#include <isr.h>
#include <task.h>
#include <paging.h>
#include <memory.h>
// The currently running task.
volatile task_t *current_task, *focused_task, *last_math_task=0;

// The start of the task linked list.
volatile task_t *ready_queue;

// Some externs are needed to access members in paging.c...
extern page_directory_t *kernel_directory;
extern page_directory_t *current_directory;
extern void alloc_frame(page_t*,int,int);
extern u32int initial_esp;
extern u32int read_eip();
u32int *pid_info;
// The next available process ID.
u32int next_pid = 1;
u32int kill_task(int pid);
extern tick;
u32int multi_tasking=0;
void update_math_state();

void inhibit_int() {
	asm volatile("cli");
}

void restart_int() {
	asm volatile("sti");
}

void initialize_tasking()
{
// Rather important stuff happening, no interrupts please!
	asm volatile("cli");
	putsbug("Loading tasking...\n");
// Initialise the first task (kernel task)
	focused_task=current_task = ready_queue = (task_t*)kmalloc(sizeof(task_t));
	current_task->id = 0;
	current_task->esp = current_task->ebp = 0;
	current_task->eip = 0;
	current_task->page_directory = current_directory;
	current_task->next = 0;
	current_task->stat = 0;
	current_task->pri = 1;
	current_task->fable=0;
	current_task->running=TASK_RUNNING;
	current_task->stack=kmalloc_a(2048);
	current_task->auth = 1000;
	current_task->used_math=0;
// Load IPC
	init_piping();
// Reenable interrupts.
	register_interrupt_handler(IRQ13, update_math_state);
	multi_tasking=1;
	asm volatile("sti");
}
int test_and_set (int new_value, int *lock_pointer);
int scheding=0;
int sched_lock=0;

void do_device_not_available(long esp, long error_code)
{
//	kprintf("DEVICE_NOT_AVAILABLE: @%d, code %d\n", esp, error_code);
	exit(11);
}

void update_math_state()
{
	if (last_math_task)
		__asm__("fnsave %0"::"m" (last_math_task->math_state));
	if (current_task->used_math)
		__asm__("frstor %0"::"m" (current_task->math_state));
	else {
		__asm__("fninit"::);
		current_task->used_math=1;
	}
	last_math_task=current_task;
}

int schedule()
{
	inhibit_int();
        while (test_and_set (1, &sched_lock)) {}

// Get the next task to run.
	current_task = current_task->next;
	if (!current_task) current_task = ready_queue;

	while((current_task->stat != 0) || (current_task->running==TASK_UNINTERRUPTABLE)){
		if(current_task->running==TASK_UNINTERRUPTABLE)
		{
			if(tick>current_task->ticks_to_sleep+current_task->start_tick_to_sleep)
			{
				current_task->running=TASK_RUNNING;
				continue;
			}
			else
				continue;
		}
		current_task->stat--;

		if(current_task->stat < 0)
			current_task->stat = current_task->pri;


		current_task = current_task->next;
		if(!current_task) current_task = ready_queue;
	}

// If we fell off the end of the linked list start again at the beginning.
	if (!current_task) current_task = ready_queue;

// Make sure the memory manager knows we've changed page directory.
	current_directory = current_task->page_directory;
	set_kernel_stack(current_task->stack);

// TODO: Is this necassary?
//	if(current_task->used_math) {
//		update_math_state();
//	}

	int i, s=0, siged=0;
	for(i=0;i<128;i++)
	{
		if(current_task->sq[i].num) {
			s=current_task->sq[i].num;
			current_task->sq[i].num=0;
//			signal_do(current_task->id, s, current_task->sq[i].dat);
			siged=1;
		}
	}
	if(current_task->running==TASK_INTERRUPTABLE)
	{
		sched_lock=0;
		schedule();
	}
	if(current_task->running==TASK_SLEEPING)
	{
		if(tick>current_task->ticks_to_sleep+current_task->start_tick_to_sleep)
		{
			current_task->running=TASK_RUNNING;
			sched_lock=0;
			restart_int();
			return 1;
		}
		else {  sched_lock=0;
			schedule();
		}
	}
	sched_lock=0;
	restart_int();
	return 1;
}

void switch_task()
{
// If we haven't initialised tasking yet, just return.
	if (!current_task)
		return;
// Read esp, ebp now for saving later on.
	u32int esp, ebp, eip;
	asm volatile("mov %%esp, %0" : "=r"(esp));
	asm volatile("mov %%ebp, %0" : "=r"(ebp));

// Read the instruction pointer. We do some cunning logic here:
// One of two things could have happened when this function exits - 
//   (a) We called the function and it returned the EIP as requested.
//   (b) We have just switched tasks, and because the saved EIP is essentially
//       the instruction after read_eip(), it will seem as if read_eip has just
//       returned.
// In the second case we need to return immediately. To detect it we put a dummy
// value in EAX further down at the end of this function. As C returns values in EAX,
// it will look like the return value is this dummy value! (0x12345).

	eip = read_eip();

// Have we just switched tasks?
	if (eip == 0x12345)
		return;

// No, we didn't switch tasks. Let's save some register values and switch.
	current_task->eip = eip;
	current_task->esp = esp;
	current_task->ebp = ebp;
	if (!schedule()) return;
	eip = current_task->eip;
	esp = current_task->esp;
	ebp = current_task->ebp;
// Here we:
// * Stop interrupts so we don't get interrupted.
// * Temporarily puts the new EIP location in ECX.
// * Loads the stack and base pointers from the new task struct.
// * Changes page directory to the physical address (physicalAddr) of the new directory.
// * Puts a dummy value (0x12345) in EAX so that above we can recognise that we've just
//   switched task.
// * Restarts interrupts. The STI instruction has a delay - it doesn't take effect until after
//   the next instruction.
// * Jumps to the location in ECX (remember we put the new EIP in there).
	asm volatile("         \
			cli;                 \
			mov %0, %%ecx;       \
			mov %1, %%esp;       \
			mov %2, %%ebp;       \
			mov %3, %%cr3;       \
			mov $0x12345, %%eax; \
			sti;                 \
			jmp *%%ecx           "
	: : "r"(eip), "r"(esp), "r"(ebp), "r"(current_directory->physicalAddr));
}

int test_and_set(int value, int *pVar)
{
    int temp;

    temp = *pVar;
    *pVar = value;
    return(temp);
}

void enter_monotasking(int pid)
{
	inhibit_int();
        while (test_and_set (1, &sched_lock)) {}
	task_t *t = (task_t*)ready_queue;
	while(t)
	{
		t->running_1=t->running;
		if(t->id != pid) t->running=TASK_INTERRUPTABLE;
		t=t->next;
	}
	multi_tasking=0;sched_lock=0;
}

void exit_monotasking()
{
	inhibit_int();
        while (test_and_set (1, &sched_lock)) {}
	task_t *t = (task_t*)ready_queue;
	while(t)
	{
		t->running=t->running_1;
		t=t->next;
	}
	multi_tasking=1;sched_lock=0;
}

int fork()
{
	int i, np;
// We are modifying kernel structures, and so cannot interrupt
		inhibit_int();
        while (test_and_set (1, &sched_lock)) {}
// Take a pointer to this process' task struct for later reference.
	task_t *parent_task = (task_t*)current_task;

// Clone the address space.
	page_directory_t *directory = clone_directory(current_directory);

// Create a new process.
	task_t *new_task = (task_t*)kmalloc(sizeof(task_t));

	new_task->id = np=next_pid;next_pid++;
	new_task->esp = new_task->ebp = 0;
	new_task->eip = 0;
	new_task->stat = 0;
	new_task->pri = parent_task->pri;
	new_task->page_directory = directory;
	new_task->auth = parent_task->auth;
	new_task->running=TASK_RUNNING;
	new_task->fable=0;new_task->used_math=0;
	if (last_math_task == current_task)
		__asm__("fnsave %0"::"m" (new_task->math_state));
	new_task->stack=kmalloc_a(2048);
	new_task->next = 0;
	for(i=0;i<MAX_SIGS;i++) {
		new_task->sig[i]=parent_task->sig[i];
		new_task->sig_auth[i]=parent_task->sig_auth[i];
	}
	for(i=0;i<128;i++)
	{
		new_task->sq[i].num=0;
		new_task->sq[i].dat=0;
		new_task->msg[i].used=0;
	}
// Add it to the end of the ready queue.
	task_t *tmp_task = (task_t*)ready_queue;
	while (tmp_task->next)
		tmp_task = tmp_task->next;
	tmp_task->next = new_task;

// This will be the entry point for the new process.
	u32int eip = read_eip();


// We could be the parent or the child here - check.
	if (current_task == parent_task)
	{
	// We are the parent, so set up the esp/ebp/eip for our child.
		u32int esp; asm volatile("mov %%esp, %0" : "=r"(esp));
		u32int ebp; asm volatile("mov %%ebp, %0" : "=r"(ebp));
		new_task->esp = esp;
		new_task->ebp = ebp;
		new_task->eip = eip;
		restart_int();
		sched_lock=0;
		return np;
	}
	else
	{
	// We are the child.
		return 0;
	}

}

int new_task(u32int (*add)(), int pri)
{
	int i;
// We are modifying kernel structures, and so cannot interrupt
		inhibit_int();
        while (test_and_set (1, &sched_lock)) {}

// Clone the address space.
	page_directory_t *directory = clone_directory(current_directory);

// Create a new process.
	task_t *new_task = (task_t*)kmalloc(sizeof(task_t));
	current_task->stack=kmalloc_a(2048);
	new_task->id = next_pid;next_pid++;
	new_task->esp = new_task->ebp = 0;
	new_task->stat = 0;
	new_task->pri = pri;
	new_task->page_directory = directory;
	new_task->auth = current_task->auth;
	new_task->next = 0;
	new_task->running=TASK_RUNNING;
	new_task->fable=0;new_task->used_math=0;
	new_task->stack=kmalloc_a(2048);
	for(i=0;i<MAX_SIGS;i++) {
		new_task->sig[i]=0;
		new_task->sig_auth[i]=0;
	}
	for(i=0;i<128;i++)
	{
		new_task->sq[i].num=0;
		new_task->sq[i].dat=0;
		new_task->msg[i].used=0;
	}
// Add it to the end of the ready queue.
	task_t *tmp_task = (task_t*)ready_queue;
	while (tmp_task->next)
		tmp_task = tmp_task->next;
	tmp_task->next = new_task;
	u32int esp; asm volatile("mov %%esp, %0" : "=r"(esp));
	u32int ebp; asm volatile("mov %%ebp, %0" : "=r"(ebp));
	new_task->esp = esp;
	new_task->ebp = ebp;
	new_task->eip = (u32int)add;
	focused_task=new_task;
	restart_int();sched_lock=0;
	return 0;
}

int exit(int code)
{
	// We are modifying kernel structures, and so cannot interrupt
	inhibit_int();
        while (test_and_set (1, &sched_lock)) {}
	if(current_task->id == 0){
//		kprintf("[KERNEL]: Kernel almost died!\n");
//		halt_system();
	}
	// Set up some basic task structs
	task_t *task_d = 0;
	task_t *task_r = (task_t*)ready_queue;
	//task_r is already initalized
	//While the next task is not NULL (end of Linked List)
	//Set test_r to the next task
	for(;task_r!=0;task_r=task_r->next)
	{
		if(task_r->next == current_task)
			task_d=task_r; // Now we have the previous task
	}
	if(!task_d){ //Make sure that the above worked
		restart_int();
		return -1;
	}
	if(last_math_task == current_task)
		last_math_task=0;
	if(focused_task==current_task)
		{
			task_r=ready_queue->next;
			while(1) {
				if(task_r)
					if(task_r!=task_d->next)
						if(task_r->fable)
							break;
				task_r=task_r->next;
				if(task_r==0)
					break;
			}
				
//			if(!task_r)
//				syscall(25, 0);
//			else
//				syscall(25, task_r->id);
		}
	pid_info[current_task->id] = kmalloc(sizeof(int));
	pid_info[current_task->id] = code;
	task_d->next = current_task->next; //Remove current_task from the list
	// Clean up memory
	kfree((void *)current_task->stack);
	kfree((void *)current_task);
	switch_task(); //We must update the scheduler, or current_task will run for a bit more
	restart_int();sched_lock=0; //Restart interrupts
	return 0;
}

u32int is_running(int pid)
{
	volatile task_t *t = ready_queue;
	int i=0;
	//asm volatile ("cli");
a:
	if(t->id == pid)
		i=1;
	else
	{
		t=t->next;
		if(t)
			goto a;
		else
			return 0;
	}
	//asm volatile ("sti");
	return i;
}

u32int sys_wait(int pid)
{
	sti(); //Restart interrupts
	while(is_running(pid)) {asm("hlt");};
}

u32int getpid()
{
	return current_task->id;
}

u32int kill_task(int pid)
{
	//kprintf("Killing %d\n", pid);
	// We are modifying kernel structures, and so cannot interrupt
		inhibit_int();
        while (test_and_set (1, &sched_lock)) {}
	// Set up some basic task structs
		if(pid == 0){
			SFKernelPanic("kernel almost died", "task/kill_task");
			for(;;);
		}
//			kprintf("[KERNEL]: Kernel almost died!\n");
//			halt_system();
			task_t *task_d = 0;
			task_t *task_r = (task_t*)ready_queue;
	//task_r is already initalized
	//While the next task is not NULL (end of Linked List)
	//Set test_r to the next task
			for(;task_r!=0;task_r=task_r->next)
			{
				if(task_r->next->id == pid)
					task_d=task_r; // Now we have the previous task
			}
			if(!task_d){ //Make sure that the above worked
				restart_int();
				return -1;
			}
				if(last_math_task == task_d)
		last_math_task=0;
			if(focused_task==task_d->next)
			{
				task_r=ready_queue->next;
				while(1) {
					if(task_r)
						if(task_r!=task_d->next)
							if(task_r->fable)
								break;
					task_r=task_r->next;
					if(task_r==0)
						break;
				}
				
//				if(!task_r)
//					syscall(25, 0);
//				else
//					syscall(25, task_r->id);
			}
			task_d->next = task_d->next->next; //Remove current_task from the list
			kfree((void *)current_task->stack);
			kfree(task_d); //Free up the memory
			switch_task(); //We must update the scheduler, or current_task will run for a bit more
			
			restart_int();sched_lock=0; //Restart interrupts
			return 0;
}
